using System;
using System.Collections.Generic;
using System.Text;
using Tomahawk.Runtime.Rendering.Interfaces;
using Tomahawk.Runtime.Rendering.Materials;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Tomahawk.Runtime.Resources;
using Microsoft.Xna.Framework.Content;
using Tomahawk.Runtime.Utils;

namespace Tomahawk.Runtime.Rendering.Objects
{


    /// <summary>
    /// 3D Model that consists of a group of meshes, each one using a customizable material
    /// </summary>
    public class MaterialMesh : MaterialEntity, IRenderable
    {
        private enum VertexStreamType
        {
            OnePassPerMesh,
            OnePassPerPart,
        }

        private VertexStreamType modelType;
        private string modelAsset;
        private string[] meshNames;
        private string[] materialNames;
        private Dictionary<ModelMeshPart, SASMaterial> materialOverrides;

        /// <summary>
        /// Constructor
        /// </summary>
        public MaterialMesh(string modelAsset, string[] meshNames, string[] materialNames)
        {
            this.modelAsset = modelAsset;

            if (meshNames.Length != materialNames.Length)
                throw new Exception("There must be one, and only one material assigned to each mesh.");

            this.meshNames = meshNames;
            this.materialNames = materialNames;
            this.materialOverrides = new Dictionary<ModelMeshPart, SASMaterial>();
        }

        /// <summary>
        /// Model asset name
        /// </summary>
        public string ModelAsset
        {
            get { return modelAsset; }
        }

        #region "Loading"

        /// <summary>
        /// Content Loading
        /// </summary>
        protected override bool OnLoad(GraphicsDevice graphics, ContentManager contentManager)
        {
            //load the model
            this.meshModel = contentManager.Load<Model>(this.modelAsset);

            //for each mesh in the model
            foreach (ModelMesh mesh in this.meshModel.Meshes)
            {
                //cannot process model with nonamed meshed
                if (mesh.Name == "") throw new Exception("Cannot use MaterialMesh with models with unnamed meshes or parts.");

                //if there is an override defined for it
                int idx = findMeshIndex(mesh.Name);
                if (idx != -1)
                {
                    //creates a material instance and initilizes it
                    SASMaterial mat = SASMaterialCollection.Load<SASMaterial>(this.materialNames[idx], contentManager);
                    
                    if (mesh.MeshParts.Count == 0)
                    {
                        throw new Exception("The mesh has no parts");
                    }

                    modelType = VertexStreamType.OnePassPerMesh;
                    int streamOffset = mesh.MeshParts[0].StreamOffset;
                    VertexDeclaration vertexDeclaration = mesh.MeshParts[0].VertexDeclaration;

                    //assign the material effect to all parts
                    foreach (ModelMeshPart part in mesh.MeshParts)
                    {
                        //index the material against its mesh part
                        this.materialOverrides.Add(part, mat);

                        //check that all the parts have the same stream offset and vertex declaration,
                        //this allows us to set the vertex stream once instead of once per part
                        if (streamOffset != part.StreamOffset || vertexDeclaration != part.VertexDeclaration)
                        {
                            modelType = VertexStreamType.OnePassPerPart;
                        }
                    }
                }
                else
                {
                    throw new Exception("You must specify a material override for mesh: " + mesh.Name);
                }
            }

            return true;
        }

        /// <summary>
        /// Find the index for the given mesh part 
        /// </summary>
        private int findMeshIndex(string name)
        {
            int retVal = -1;
            for (int i = 0; i < this.meshNames.Length; i++)
                if (this.meshNames[i] == name) retVal = i;
            return retVal;
        }

        #endregion

        #region "Rendering"

        /// <summary>
        /// Render the model 
        /// </summary>
        public void Render(ref Matrix worldMatrix, GraphicsDevice myDevice)
        {
            foreach (ModelMesh mesh in this.meshModel.Meshes)
            {
                if (modelType == VertexStreamType.OnePassPerMesh)
                {
                    SASMaterial.Begin(mesh.VertexBuffer, mesh.IndexBuffer, mesh.MeshParts[0].StreamOffset, mesh.MeshParts[0].VertexDeclaration, myDevice);
                }

                //prepare all the effects of the model
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    if (modelType == VertexStreamType.OnePassPerPart)
                    {
                        SASMaterial.Begin(mesh.VertexBuffer, mesh.IndexBuffer, part.StreamOffset, part.VertexDeclaration, myDevice);
                    }

                    SASMaterial mat = this.materialOverrides[part];
                    mat.Update(Engine.Instance.EffectEnvironment);

                    RaiseOnPrepareMaterial(this, mat);

                    mat.DrawMesh(mesh, part, myDevice);

                    if (modelType == VertexStreamType.OnePassPerPart)
                    {
                        SASMaterial.End();
                    }
                }

                if (modelType == VertexStreamType.OnePassPerMesh)
                {
                    SASMaterial.End();
                }
            }

        }

        #endregion

    }

}
